package git.soulbgm.utils;

import java.io.*;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.spi.FileSystemProvider;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 提供一些常用的属性文件相关的方法
 *
 * @author SoulBGM
 * @version V1.0
 * @date 2020-11-06 17:27:16
 */
public class PropertiesUtil {

    /**
     * 左斜杠
     */
    public final static String LEFT_SLANTING_BAR = "/";
    /**
     * 右斜杠
     */
    private final static String RIGHT_SLANTING_BAR = "\\\\";

    private static final String URL_PROTOCOL_JAR = "jar";
    private static final String URL_PROTOCOL_ZIP = "zip";
    private static final String URL_PROTOCOL_VFSZIP = "vfszip";
    private static final String URL_PROTOCOL_WSJAR = "wsjar";

    /**
     * 获得文件夹下全部的文件内容
     *
     * @param folderPath 文件夹地址
     * @return key：文件名称  val：文件内容
     */
    public static Map<String, String> getFolderAll(String folderPath) throws IOException {
        folderPath = transformSlantingBar(folderPath);
        URL url = getResource(folderPath);
        Map<String, String> map = new HashMap<>(20);
        if (isJarURL(url)) {
            String jarPath = url.toString().substring(0, url.toString().indexOf("!/") + 2);
            URL jarURL = new URL(jarPath);
            JarURLConnection jarCon = (JarURLConnection) jarURL.openConnection();
            JarFile jarFile = jarCon.getJarFile();
            Enumeration<JarEntry> jarEntrys = jarFile.entries();
            while (jarEntrys.hasMoreElements()) {
                JarEntry entry = jarEntrys.nextElement();
                String name = entry.getName();
                if (name.startsWith(folderPath) && !folderPath.equals(name)) {
                    byte[] bytes = copyToByteArray(getInputStream(name));
                    String content = new String(bytes, Charset.forName("UTF-8"));
                    if (content != null && !"".equals(content)) {
                        map.put(name.substring(name.lastIndexOf(folderPath) + folderPath.length()), content);
                    }
                }
            }
        } else {
            byte[] data = copyToByteArray(getInputStream(folderPath));
            String fileNames = new String(data, StandardCharsets.UTF_8);
            String[] split = fileNames.split("\n|\r\n|\r");
            for (String s : split) {
                String filePath = folderPath + File.separator + s;
                URL fileUrl = getResource(filePath);
                String path = fileUrl.getPath().replaceAll("%5c", "/");
                File file = new File(path);
                if (file.isFile()) {
                    byte[] bytes = copyToByteArray(getInputStream(filePath));
                    String content = new String(bytes, Charset.forName("UTF-8"));
                    map.put(s, content);
                } else {
                    Map<String, String> childFolder = setKeyPrefix(getFolderAll(filePath), s + File.separator);
                    if (childFolder != null) {
                        map.putAll(childFolder);
                    }
                }
            }
        }
        return map;
    }

    private static Map<String, String> setKeyPrefix(Map<String, String> folderAll, String prefix) {
        if (folderAll == null) {
            return null;
        }
        List<String> delKey = new ArrayList<>(folderAll.size());
        Object[] keyArray = folderAll.keySet().toArray();
        for (Object name : keyArray) {
            folderAll.put(prefix + name, folderAll.get(name));
            delKey.add(String.valueOf(name));
        }

        for (String k : delKey) {
            folderAll.remove(k);
        }
        return folderAll;
    }

    /**
     * 获得相对路径地址的URL
     *
     * @param path 项目的相对路径
     * @return URL
     */
    private static URL getResource(String path) {
        return PropertiesUtil.class.getClassLoader().getResource(path);
    }

    /**
     * 获取配置文件内容 默认编码utf-8
     *
     * @param path 配置文件地址
     * @return 配置文件内容
     */
    public static String getConfigurationFileContext(String path) {
        return getConfigurationFileContext(path, "UTF-8");
    }

    /**
     * 获取配置文件内容
     *
     * @param path 配置文件地址
     * @return 配置文件内容
     */
    public static String getConfigurationFileContext(String path, String charsetName) {
        path = transformSlantingBar(path);
        String content = "";
        try {
            byte[] bytes = copyToByteArray(getInputStream(path));
            content = new String(bytes, Charset.forName(charsetName));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return content;
    }

    /**
     * 读取某个前缀的所有值
     *
     * @param filePath 读取的属性文件
     * @param prefix   统一前缀
     * @param annotate 注释的前缀标记
     * @return 符合前缀字符串的所有配置信息(key是去掉前缀的)
     */
    public static Map<String, String> getPrefixProperties(String filePath, String prefix, String annotate) {
        String content = getConfigurationFileContext(filePath);
        String[] split = content.split("\n|\r\n|\r");
        Map<String, String> map = new HashMap<>(split.length);
        for (String s : split) {
            if ("".equals(s.trim())) {
                continue;
            }
            if (annotate != null && !"".equals(annotate)) {
                if (s.startsWith(annotate)) {
                    continue;
                }
            }
            if (prefix != null && !"".equals(prefix)) {
                if (!s.startsWith(prefix)) {
                    continue;
                } else {
                    s = s.replaceAll(prefix + ".", "");
                }
            }
            int index = s.indexOf("=");
            String key = s.substring(0, index);
            String val = s.substring(index + 1);
            map.put(key, val);
        }
        return map;
    }

    /**
     * 读取Properties的全部信息
     *
     * @param filePath 读取的属性文件
     * @param annotate 注释的前缀标记
     * @return 返回所有的属性 key:value<>key:value
     */
    public static Map<String, String> getAllProperties(String filePath, String annotate) {
        return getPrefixProperties(filePath, null, annotate);
    }

    public static byte[] copyToByteArray(InputStream in) throws IOException {
        if (in == null) {
            return new byte[0];
        } else {
            ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
            copy((InputStream) in, (OutputStream) out);
            return out.toByteArray();
        }
    }

    private static int copy(InputStream in, OutputStream out) throws IOException {
        notNull(in, "No InputStream specified");
        notNull(out, "No OutputStream specified");

        int byteCount = 0;
        try {
            byte[] buffer = new byte[4096];
            int bytesRead;
            for (boolean var4 = true; (bytesRead = in.read(buffer)) != -1; byteCount += bytesRead) {
                out.write(buffer, 0, bytesRead);
            }
            out.flush();
        } finally {
            in.close();
            out.close();
        }
        return byteCount;
    }

    public static InputStream getInputStream(String path) throws IOException {
        InputStream is = null;
        if (isJarURL(path)) {
            // 使用springboot打的jar的地址前面需要加"BOOT-INF/classes/" 或者直接使用org.springframework.core.io.ClassPathResource获取
            /*ClassPathResource cpr = new ClassPathResource(path); is = cpr.getInputStream();*/
            is = ClassLoader.getSystemResourceAsStream("BOOT-INF/classes/" + path);
        } else {
            is = ClassLoader.getSystemResourceAsStream(path);
        }
        if (is == null) {
            File file = new File(path);
            if (file.isFile()) {
                is = new FileInputStream(file);
            } else if (file.isDirectory()) {
                String[] fileNameArray = file.list();
                StringBuilder fileName = new StringBuilder();
                if (fileNameArray != null) {
                    for (int i = 0; i < fileNameArray.length; i++) {
                        fileName.append(fileNameArray[i]);
                        if (i != fileNameArray.length - 1) {
                            fileName.append("\n");
                        }
                    }
                }
                is = new ByteArrayInputStream(fileName.toString().getBytes(Charset.forName("UTF-8")));
            }
            if (is == null) {
                throw new FileNotFoundException(getDescription(path) + " cannot be opened because it does not exist");
            }
        }
        return is;
    }

    private static String getDescription(String path) {
        StringBuilder builder = new StringBuilder("class path resource [");
        String pathToUse = path;
        if (pathToUse.startsWith(LEFT_SLANTING_BAR)) {
            pathToUse = pathToUse.substring(1);
        }
        builder.append(pathToUse);
        builder.append(']');
        return builder.toString();
    }

    /**
     * resource地址是否是在jar中
     *
     * @param path 地址
     * @return 是jar包（压缩包）true 否则false
     */
    public static boolean isJarURL(String path) {
        path = transformSlantingBar(path);
        return isJarURL(getResource(path));
    }

    /**
     * Url是否是jar文件
     *
     * @param url 地址
     * @return 是jar包（压缩包）true 否则false
     */
    public static boolean isJarURL(URL url) {
        if (url == null) {
            return false;
        }
        String protocol = url.getProtocol();
        return (URL_PROTOCOL_JAR.equals(protocol) || URL_PROTOCOL_ZIP.equals(protocol) ||
                URL_PROTOCOL_VFSZIP.equals(protocol) || URL_PROTOCOL_WSJAR.equals(protocol));
    }

    private static void notNull(Object object, String message) {
        if (object == null) {
            throw new IllegalArgumentException(message);
        }
    }

    private static String transformSlantingBar(String folderPath) {
        // 首先进行斜杠转换 防止斜杠书写错误导致文件找不到
        if (folderPath != null) {
            folderPath = folderPath.replaceAll(RIGHT_SLANTING_BAR, LEFT_SLANTING_BAR);
        }
        return folderPath;
    }

    /**
     * 扫描包
     *
     * @param packageName 扫描包名
     * @return 扫描包的所有.class后缀的类、接口、注解、枚举...的class的全名称(包名.名称)
     */
    public static String[] scanPackage(String packageName) throws Exception {
        packageName = packageName.replaceAll("\\.", "/");
        URL url = Thread.currentThread().getContextClassLoader().getResource(packageName);
        if (url == null) {
            return null;
        }
        url = new URL(URLDecoder.decode(url.toString(), "UTF-8"));
        FileSystemProvider provider = null;
        if (url.getProtocol().equals("jar")) {
            provider = getZipFSProvider();
            if (provider != null) {
                try (FileSystem fs = provider.newFileSystem(Paths.get(url.getPath().replaceFirst("file:/", "").replaceFirst("!.*", "")), new HashMap<>())) {
                    return walkFileTree(fs.getPath(packageName), null).toArray(new String[0]);
                } catch (Exception e) {
                    throw e;
                }
            }
        } else if (url.getProtocol().equals("file")) {
            int end = url.getPath().lastIndexOf(packageName);
            String basePath = url.getPath().substring(1, end);
            return walkFileTree(Paths.get(url.getPath().replaceFirst("/", "")), Paths.get(basePath)).toArray(new String[0]);
        }
        return null;
    }

    /**
     * 获得文件树
     *
     * @param path     地址
     * @param basePath 父类文件地址
     * @return
     */
    private static List<String> walkFileTree(Path path, Path basePath) throws IOException {
        final List<String> result = new ArrayList<>();
        Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
            private String packageName = Objects.isNull(basePath) ? "" : basePath.toString();

            @Override
            public FileVisitResult visitFile(Path arg0, BasicFileAttributes arg1) throws IOException {
                if (arg0.toString().endsWith(".class")) {
                    result.add(arg0.toString().replace(packageName, "").substring(1).replace("\\", "/").replace(".class", "").replace("/", "."));
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult preVisitDirectory(Path arg0, BasicFileAttributes arg1) throws IOException {
                return FileVisitResult.CONTINUE;
            }

        });
        return result;
    }

    public static FileSystemProvider getZipFSProvider() {
        for (FileSystemProvider provider : FileSystemProvider.installedProviders()) {
            if ("jar".equals(provider.getScheme())) {
                return provider;
            }
        }
        return null;
    }

}
